from decimal import Decimal
from os import path
from math import pow, sqrt
import libxml2

from warnings import warn
try:
	import RDF
except ImportError:
	warn("Redland RDF missing. Disabling RDF features", RuntimeWarning)
	RDF = None

## fail gracefully if we don't have supporting libraries 
## such as Python Scientific, etc.
try:
	import orbit
	from orbit import earth_radius
except ImportError:
	def earth_radius(*av, **kw):
		return 6371001

from error import *
from envxml import newEnvisatRdfDoc, findChild
import cqfd

parse_map = {
	### product metadata
	"PRODUCT" : ("strNode", "dc", "identifier"),
	"PROC_STAGE" : ("strNode", "envisat", "procStage"),
	"REF_DOC" : ("resNode", "envisat", "refDoc") ,
	### processing information
	"ACQUISITION_STATION" : ("strNode", "envisat", "acquisitionStation"),
	"PROC_CENTER" : ("strNode", "envisat", "procCentre"),
	"PROC_TIME" : ("dateNode", "dc", "created"),
	"SOFTWARE_VER" : ("strNode", "envisat", "softwareVersion"),
	### sensing information
	"SENSING_START" : ("dateNode", "ical", "dtstart"),
	"SENSING_STOP" : ("dateNode", "ical", "dtend"),
	### orbit information
	"PHASE" : ("strNode", "envisat", "orbitPhase"),
	"CYCLE" : ("numNode", "envisat", "orbitCycle"),
	"REL_ORBIT" : ("numNode", "envisat", "relOrbit"),
	"ABS_ORBIT" : ("numNode", "envisat", "absOrbit"),
	"STATE_VECTOR_TIME" : ("dateNode", "envisat", "stateVectorTime"),
	"DELTA_UT1" : ("numNode", "envisat", "deltaUT1"),
	"X_POSITION" : ("numNode", "envisat", "xPosition"),
	"Y_POSITION" : ("numNode", "envisat", "yPosition"),
	"Z_POSITION" : ("numNode", "envisat", "zPosition"),
	"X_VELOCITY" : ("numNode", "envisat", "xVelocity"),
	"Y_VELOCITY" : ("numNode", "envisat", "yVelocity"),
	"Z_VELOCITY" : ("numNode", "envisat", "zVelocity"),
	"VECTOR_SOURCE" : ("strNode", "envisat", "vectorSource"),
	### time information
	"UTC_SBT_TIME" : ("dateNode", "envisat", "utcSatelliteTime"),
	"SAT_BINARY_TIME" : ("numNode", "envisat", "satBinaryTime"),
	"CLOCK_STEP" : ("numNode", "envisat", "clockStep"),
	### leap year parameters
	"LEAP_UTC" : ("dateNode", "envisat", "leapUTC"),
	"LEAP_SIGN" : ("numNode", "envisat", "leapSign"),
	"LEAP_ERR" : ("numNode", "envisat", "leapErr"),
	### product information
	"PRODUCT_ERR" : ("numNode", "envisat", "error"),
	"TOT_SIZE" : ("numNode", "envisat", "totalSize"),
	"SPH_SIZE" : ("numNode", "envisat", "sphSize"),
	"NUM_DSD" : ("numNode", "envisat", "numDSD"),
	"DSD_SIZE" : ("numNode", "envisat", "dsdSize"),
	"NUM_DATA_SETS" : ("numNode", "envisat", "numDataSets"),
	### specific header
	"SPH_DESCRIPTOR" : ("strNode", "envisat", "sphDescriptor"),
	### MERIS specific header
	"STRIPLINE_CONTINUITY_INDICATOR" : ("numNode", "envisat", "striplineContinuityIndicator"),
	"SLICE_POSITION" : ("numNode", "envisat", "slicePosition"),
	"NUM_SLICES" : ("numNode", "envisat", "numSlices"),
	"FIRST_LINE_TIME" : ("dateNode", "envisat", "firstLineTime"),
	"LAST_LINE_TIME" : ("dateNode", "envisat", "lastLineTime"),
	"FIRST_FIRST_LAT" : ("numNode", "envisat", "firstFirstLat"),
	"FIRST_FIRST_LONG" : ("numNode", "envisat", "firstFirstLong"),
	"FIRST_MID_LAT" : ("numNode", "envisat", "firstMidLat"),
	"FIRST_MID_LONG" : ("numNode", "envisat", "firstMidLong"),
	"FIRST_LAST_LAT" : ("numNode", "envisat", "firstLastLat"),
	"FIRST_LAST_LONG" : ("numNode", "envisat", "firstLastLong"),
	"LAST_FIRST_LAT" : ("numNode", "envisat", "lastFirstLat"),
	"LAST_FIRST_LONG" : ("numNode", "envisat", "lastFirstLong"),
	"LAST_MID_LAT" : ("numNode", "envisat", "lastMidLat"),
	"LAST_MID_LONG" : ("numNode", "envisat", "lastMidLong"),
	"LAST_LAST_LAT" : ("numNode", "envisat", "lastLastLat"),
	"LAST_LAST_LONG" : ("numNode", "envisat", "lastLastLong"),
	### GOMOS specific header
	"START_TIME" : ("dateNode", "envisat", "startTime"),
	"STOP_TIME" : ("dateNode", "envisat", "stopTime"),
	"START_TANGENT_LAT" : ("numNode", "envisat", "startTangentLat"),
	"START_TANGENT_LONG" : ("numNode", "envisat", "startTangentLong"),
	"STOP_TANGENT_LAT" : ("numNode", "envisat", "stopTangentLat"),
	"STOP_TANGENT_LONG" : ("numNode", "envisat", "stopTangentLong"),
	"OCC_DURATION" : ("numNode", "envisat", "occDuration"),
	"SAMP_DURATION" : ("numNode", "envisat", "sampDuration"),
	"NUM_MEASURE" : ("numNode", "envisat", "numMeasure"),
	"INS_STATUS" : ("numNode", "envisat", "insStatus"),
	"OCC_NUM" : ("numNode", "envisat", "occNum"),
	"STAR": ("strNode", "envisat", "star"),
	"STAR_ID" : ("numNode", "envisat", "starId"),
	"STAR_MAG" : ("numNode", "envisat", "starMag"),
	"STAR_TEMP" : ("numNode", "envisat", "starTemp"),
	"STAR_DIRECT1" : ("strNode", "envisat", "starDirect1"), ### FIXME
	"STAR_DIRECT2" : ("strNode", "envisat", "starDirect2"), ### FIXME
	"BRIGHT_LIMB" : ("numNode", "envisat", "brightLimb"),
	### MIPAS specific header
	"FIRST_TANGENT_LAT" : ("numNode", "envisat", "firstTangentLat"),
	"FIRST_TANGENT_LONG" : ("numNode", "envisat", "firstTangentLong"),
	"LAST_TANGENT_LAT" : ("numNode", "envisat", "lastTangentLat"),
	"LAST_TANGENT_LONG" : ("numNode", "envisat", "lastTangentLong"),
	"TOT_SWEEPS" : ("numNode", "envisat", "totalSweeps"),
	"TOT_SCANS" : ("numNode", "envisat", "totalScans"),
	"TOT_NOM_SCANS" : ("numNode", "envisat", "totalNomScans"),
	"NUM_SWEEPS_PER_SCAN" : ("numNode", "envisat", "numSweepsPerScan"),
	"SCANS_PER_OFF_CAL" : ("numNode", "envisat", "scansPerOffCal"),
	"TOT_SP_SCANS" : ("numNode", "envisat", "totalSpScans"),
	"FRINGES_PER_SCENE" : ("numNode", "envisat", "fringesPerScene"),
	"NUM_POINTS_PER_BAND" : ("multiNumNode", "envisat", "numPointsPerBand"),
	"FIRST_WAVENUM" : ("strNode", "envisat", "firstWavenum"), ### FIXME
	"LAST_WAVENUM" : ("strNode", "envisat", "lastWavenum"), ### FIXME
	"NUM_NESR_PNTS" : ("numNode", "envisat", "numNesrPoints"),
	"NESR_FIRST_WAVENUM" : ("strNode", "envisat", "nesrFirstWavenum"), ### FIXME
	"NESR_LAST_WAVENUM" : ("strNode", "envisat", "nesrLastWavenum"), ### FIXME
	"SWEEP_ID" : ("numNode", "envisat", "sweepId"),
	"MAX_PATH_DIFF" : ("numNode", "envisat", "maxPathDiff"),
	"SLICE_POSITION" : ("numNode", "envisat", "slicePosition"),
	"NUM_SLICES" : ("numNode", "envisat", "numSlices"),
	"NUM_SCANS" : ("numNode", "envisat", "numScans"),
	"NUM_LOS_GEOMS" : ("numNode", "envisat", "numLosGeoms"),
	"NUM_SCANS_PER_DS" : ("numNode", "envisat", "numScansPerDs"),
	"NUM_SCANS_PROC" : ("numNode", "envisat", "numScansProc"),
	"NUM_SP_NOT_PROC" : ("numNode", "envisat", "numSpNotProc"),
	"NUM_SPECTRA" : ("numNode", "envisat", "numSpectra"),
	"NUM_SPECTR_PROC" : ("numNode", "envisat", "numSpectraProc"),
	"NUM_GAIN_CAL" : ("numNode", "envisat", "numGainCal"),
	"TOT_GRANULES" : ("numNode", "envisat", "totalGranules"),
	"MAX_PATH_DIFF" : ("numNode", "envisat", "maxPathDiff"),
	"ORDER_OF_SPECIES" : ("strNode", "envisat", "orderOfSpecies"), ### fixme list
	## transfer error info
	"TRANS_ERR_FLAG" : ("numNode", "envisat", "transErrFlag"),
	"FORMAT_ERR_FLAG" : ("numNode", "envisat", "formatErrFlag"),
	"DATABASE_FLAG" : ("numNode", "envisat", "databaseFlag"),
	"COARSE_ERR_FLAG" : ("numNode", "envisat", "coarseErrFlag"),
	"ECMWF_TYPE" : ("numNode", "envisat", "ecmwfType"),
	"NUM_TRANS_ERR" : ("numNode", "envisat", "numTransErr"),
	"NUM_FORMAT_ERR" : ("numNode", "envisat", "numFormatErr"),
	"TRANS_ERR_THRESH" : ("numNode", "envisat", "transferErrThreshold"),
	"FORMAT_ERR_THRESH" : ("numNode", "envisat", "formatErrThreshold"),
	## band info"
	"NUM_BANDS" : ("numNode", "envisat", "numBands"),
	"BAND_WAVELEN" : ("multiNumNode", "envisat", "bandWavelengthList"),
	"BANDWIDTH" : ("multiNumNode", "envisat", "bandWidthList"),
	"INST_FOV" : ("numNode", "envisat", "instFOV"),
	"PROC_MODE" : ("numNode", "envisat", "procMode"),
	"OFFSET_COMP" : ("numNode", "envisat", "offsetComp"),
	"LINE_TIME_INTERVAL" : ("numNode", "envisat", "lineTimeInterval"),
	"LINE_LENGTH" : ("numNode", "envisat", "lineLength"),
	"LINES_PER_TIE_PT" : ("numNode", "envisat", "linesPerTiePoint"),
	"SAMPLES_PER_TIE_PT" : ("numNode", "envisat", "samplesPerTiePoint"),
	"COLUMN_SPACING" : ("numNode", "envisat", "columnSpacing"),
	## data set information
	"DS_NAME" : ("strNode", "dc", "title"),
	"DS_TYPE" : ("strNode", "envisat", "dataSetType"),
	"FILENAME" : ("strNode", "envisat" ,"filename"),
	"DS_OFFSET" : ("numNode", "envisat", "offset"),
	"DS_SIZE" : ("numNode", "envisat", "size"),
	"NUM_DSR" : ("numNode", "envisat", "numDSR"),
	"DSR_SIZE" : ("numNode", "envisat", "dsrSize"),
}

sph_map = {
	"MER_RR__1P SPECIFIC HEADER" : ("processMeris", "MERIS Level 1"),
	"MER_RR__2P SPECIFIC HEADER" : ("processMeris", "MERIS Level 2"),
	"GOM_TRA_1P SPECIFIC HEADER" : ("processGomos", "GOMOS Level 1"),
	"MIPAS_LEVEL_1B_PRODUCT" : ("processMipas", "MIPAS Level 1"),
	"MIP_NL__2P SPECIFIC HEADER" : ("processMipas", "MIPAS Level 2"),
}

class EnvisatProduct(object):
	"""
	EnvisatProduct represents an ENVISAT product file and has methods
	to read the file header and return an RDF/XML model (using libxml2)
	and to read embedded data.

	The constructor takes a configuration object (see EnvisatConfig in
	envisat/config.py) and a filename. Optionally a pre-constructed
	XML model may be passed in.
	"""
	def __init__(self, config, filename, xml = None):
		self.config = config
		self.filename = filename
		self.__fp__ = None
		self.__xml__ = xml
		self.__offset__ = None
		self.__size__ = None

	def open(self):
		"""
		Open the file or return the file pointer reset to the zero
		position (beginning of file).
		"""
		if not self.__fp__:
			self.__fp__ = open(self.filename, "r")
		else:
			self.__fp__.seek(0)
		return self.__fp__

	def close(self):
		"""
		Close the file pointer
		"""
		if self.__fp__:
			self.__fp__.close()
			self.__fp__ = None

	def about(self):
		"""
		Return a URL string suitable for use in an RDF "about"
		statement. This makes use if the "distribution" configuration
		parameter in the envisat section, as in

		[envisat]
		distribution=http://www.example.com/distroot/

		The configured file path is appended to this value in order
		to produce the canonical URL for this file.
		"""
		if self.config.has_option("envisat", "distribution"):
			dist_root = self.config.get("envisat", "distribution")
		else:
			dist_root = "http://www.example.com/distroot/"
		filename = self.filename.lstrip("/")
		return path.join(dist_root, filename)

	@property
	def xml(self):
		"""
		Return the RDF/XML model or construct it if necessary.
		"""
		if self.__xml__:
			return self.__xml__
		self.__xml__ = newEnvisatRdfDoc("1.0", self.config)

		root = self.__xml__.root
		root.setBase(self.about())

		self.parse_map = {}
		for k in parse_map:
			parser = parse_map[k]
			fname, args  = parser[0], parser[1:]
			self.parse_map[k] = getattr(self.__xml__, fname)(*args)
		fp = self.open()

		node = self.xml.newNsNode("dclite4g", "DataSet")
		node.setNsProp(self.xml.getNs("rdf"), "about", self.about())
		root.addChild(node)

		## product
		self.processChunk(root, fp, node)
		## processing
		self.processChunk(root, fp, node)
		## sensing
		self.processChunk(root, fp, node)
		## orbit
		self.processChunk(root, fp, node)
		self.processOrbit(root, fp, node)
		## time
		self.processChunk(root, fp, node)
		## leap
		self.processChunk(root, fp, node)
		## product info
		self.processChunk(root, fp, node)

		self.processSph(root, fp, node)

		## transfer
		self.processChunk(root, fp, node)
		## bands
		self.processChunk(root, fp, node)

		i = 0
		while True:
			try:
				ds = self.xml.newNsNode("envisat", "dataset")
				self.processChunk(root, fp, ds)
				## kludge: sometimes we get an empty dataset.
				## really need better end of header handling
				if not ds.children:
					ds.unlinkNode()
					continue
				ds.setNsProp(self.xml.getNs("rdf"), "ID", "dataset-%s" % i)
				product = self.xml.newNsNode("envisat", "product")
				product.setNsProp(self.xml.getNs("rdf"), "resource", self.about())
				ds.addChild(product)
				root.addChild(ds)
				i = i + 1
			except EndProc:
				break

		self.close()
		return self.__xml__

	def processChunk(self, root, fp, node):
		"""
		Process a list of key-value pairs from the ENVISAT product
		file, terminated with an empty line.
		"""
		while True:
			line = fp.readline()
			if line == '':
				raise EndProc
			line = line.strip()
			if line == '':
				break
			try:
				k,v = line.split("=")
			except ValueError:
				raise EndProc
			## kludge -- on the off chance there is a "=" in the binary
			## data. Should do this better by checking if we are past
			## the end of the header.
			from string import ascii_letters, digits
			if k[0] not in ascii_letters + digits:
				raise EndProc
			try:
				self.parse_map[k](node, v)
			except KeyError:
				raise FormatError("%s: %s" % (nodeName, line))

	def processOrbit(self, root, fp, node):
		"""
		Process orbit specific data.
		
		Turn the, rather arbitrary, xPosition, yPosition, zPosition
		and xVelocity, yVelocity, zVelocity children of the given node,
		if they exist into better-behaved coordinates values.
		"""
		for point in ("Position", "Velocity"):
			coord = []
			for axis in ("x", "y", "z"):
				child = findChild(node, axis + point)
				coord.append(child.content)
				child.unlinkNode()
				child = findChild(node, axis + point + "Units")
				units = child.content
				child.unlinkNode()
			coords = ",".join(coord)
			child = self.xml.newNsNode("envisat", point)
			child.setContent(coords)
			node.addChild(child)
			child = self.xml.newNsNode("envisat", point + "Units")
			child.setContent(units)
			node.addChild(child)
			if point == "Position":
				altitude = sqrt(sum(map(lambda x: pow(Decimal(x), 2), coord)))
				child = self.xml.newNsNode("envisat", "Altitude")
				child.setContent("%s" % altitude)
				node.addChild(child)
				child = self.xml.newNsNode("envisat", "AltitudeUnits")
				child.setContent(units)
				node.addChild(child)


	def processSph(self, root, fp, node):
		"""
		Process a specific header.
		"""
		line = fp.readline().strip()
		try:
			k,v = line.split("=")
		except ValueError:
			raise FormatError("SPH got: %s" % (line,))
		if k != "SPH_DESCRIPTOR":
			raise FormatError("Expected SPH_DESCRIPTOR got: %s" % (k,))
		v = v.replace('"', '').strip()
		try:
			procname, title = sph_map[v]
		except KeyError:
			raise FormatError("No Specific Header for: %s" % (v,))
		try:
			proc = getattr(self, procname)
		except AttributeError:
			raise FormatError("No Processor %s for %s" % (procname, v))

		self.processChunk(root, fp, node)

		dsID = v.split(" ")[0]

		## kludge to ensure stable product URI
		if self.config.has_option("envisat", "distribution"):
			dist_root = self.config.get("envisat", "distribution")
		else:
			dist_root = "http://www.example.com/distroot/"
		product_base = dist_root + "EO_DATA/" + dsID

		Series = self.xml.newNsNode("dclite4g", "Series")
		Series.setNsProp(self.xml.getNs("rdf"), "about", product_base)
		titleNode = self.xml.newNsNode("dc", "title")
		titleNode.setContent(title)
		Series.addChild(titleNode)
		root.addChild(Series)

		dataSet = self.xml.newNsNode("dclite4g", "series")
		dataSet.setNsProp(self.xml.getNs("rdf"), "resource", product_base)
		node.addChild(dataSet)
		
		formatNode = self.xml.newNsNode("dclite4g", "fileFormat")
		formatNode.setContent(dsID)
		node.addChild(formatNode)
		
		proc(root, fp, node)
		
	def processMeris(self, root, fp, node):
		"""
		Process the MERIS specific header
		"""
		for line in ("first", "last"):
			for point in ("First", "Mid", "Last"):
				self.sweetPoint(node, line + point)
				
		if self.config.has_option("envisat", "coverage"):
			coverage_type = self.config.get("envisat", "coverage")
			if coverage_type == "cqfd":
				wkt = cqfd.polygons(self.config, self.filename,
							"Tie_points_ADS long_tie_pt lat_tie_pt -g")
			else:
				raise ConfigError("Invalid [envisat]/coverage %s" % coverage_type)
			region = self.xml.newNsNode("dct", "spatial")
			region.setContent(wkt)
			node.addChild(region)


	def processGomos(self, root, fp, node):
		"""
		Process the GOMOS specific header
		"""
		for point in ("start", "stop"):
			self.sweetPoint(node, point + "Tangent")
		node = self.processChunk(root, fp, "gomos")
		node.setNsProp(self.xml.getNs("rdf"), "about", self.about())

	def processMipas(self, root, fp, node):
		"""
		Process the MIPAS specific header
		"""
		for point in ("first", "last"):
			self.sweetPoint(node, point + "Tangent")
		node = self.processChunk(root, fp, "mipas")
		node.setNsProp(self.xml.getNs("rdf"), "about", self.about())

	def sweetPoint(self, node, name):
		"""
		Given a node known to have children of the form,

		<nameLong>1</nameLong>
		<nameLongUnits>units</nameLongUnits>
		<nameLat>2</nameLat>
		<nameLatUnits>units</nameLatUnits>

		transform to WKT

		<name>POINT(1 2)</name>

		Units are not interpreted and are simply dropped
		"""
		longNode = findChild(node, name + "Long")
		longNodeUnits = findChild(node, name + "LongUnits")
		latNode = findChild(node, name + "Lat")
		latNodeUnits = findChild(node, name + "LatUnits")
		
		longNode.unlinkNode()
		longNodeUnits.unlinkNode()
		latNode.unlinkNode()
		latNodeUnits.unlinkNode()

		newNode = self.xml.newNsNode("envisat", name)
		newNode.setContent("POINT(%s %s)" % (longNode.content, latNode.content))

		node.addChild(newNode)

	def parse_into_model(self, model):
		"""
		Take the statements present in this file and add them to the model
		if they are configured in the [predicates] section of the
		configuration file. See EnvisatConfig in envisat/config.py
		"""
		## disabled if RDF is not present
		if not RDF:
			warn("RDF not present", RuntimeWarning)
			return
		parser = RDF.Parser()
		memstore = RDF.Storage(storage_name="memory", name="memory", options_string="")
		memory = RDF.Model(memstore)
		parser.parse_string_into_model(memory, self.xml.serialize(), self.about())
		context = RDF.Node(RDF.Uri(self.about()))
		for s in memory.find_statements(RDF.Statement(None, None, None)):
			if self.config.has_predicate(s.predicate.uri):
				model.add_statement(s, context)

	def flush(self, model):
		"""
		Flush references to this file in the given model.
		"""
		## disabled if RDF is not present
		if not RDF:
			warn("RDF not present", RuntimeWarning)
			return
		### first remove statements about us directly
		subject = RDF.Uri(self.about())
		query = RDF.Statement(subject, None, None)
		for statement in model.find_statements(query):
			del model[statement]
		### then remove statements about things that refer to us as a product
		### (datasources do this)
		predicate = RDF.Uri(self.config.get("namespaces", "envisat") + "product")
		object = RDF.Uri(self.about())
		query = RDF.Statement(None, predicate, object)
		for result in model.find_statements(query):
			query = RDF.Statement(result.subject, None, None)
			for statement in model.find_statements(query):
				del model[statement]

	def exists(self, model):
		"""
		Return true if this file (identified by self.about()) is
		present in the given model.
		"""
		## disabled if RDF is not present
		if not RDF:
			warn("RDF not present", RuntimeWarning)
			return
		subject = RDF.Uri(self.about())
		predicate = RDF.Uri(self.config.get("namespaces", "dc") + "identifier")
		object = path.basename(self.filename)
		statement = RDF.Statement(subject, predicate, object)
		return model.contains_statement(statement)

	def _datasets(self):
		dataset_uri = RDF.Uri(self.about())
		nds_uri = Meris.Uri("numDataSets")
		s = RDF.Statement(dataset_uri, nds_uri, None)
		nds = int(self.get_statement(s).object.literal_value["string"])

		for i in range(nds):
			id = self.about() + "#dataset-%s" % i
			dataset_uri = RDF.Uri(id)

			name_uri = Meris.Uri("name")
			s = RDF.Statement(dataset_uri, name_uri, None)
			name = self.get_statement(s).object.literal_value["string"]

			filename_uri = Meris.Uri("filename")
			s = RDF.Statement(dataset_uri, filename_uri, None)
			filename = self.get_statement(s).object

			offset_uri = Meris.Uri("offset")
			s = RDF.Statement(dataset_uri, offset_uri, None)
			offset = int(self.get_statement(s).object.literal_value["string"])

			size_uri = Meris.Uri("size")
			s = RDF.Statement(dataset_uri, size_uri, None)
			size = int(self.get_statement(s).object.literal_value["string"])

			yield Dataset(name, self.filename, offset, size)
	def datasets(self):
		return list(self._datasets())

class Dataset(object):
	def __init__(self, name, filename, offset, size):
		self.name = name
		self.filename = filename
		self.offset = offset
		self.size = size
		self.eoffset = self.offset + self.size
		self.__fp__ = None

	def __str__(self):
		return "%s (%s:%s)" % (self.name, self.offset, self.size)

	def open(self):
		if self.__fp__ is None:
			self.__fp__ = open(self.filename, "rb")
			self.seek(self.offset)
		return self.__fp__

	def seek(self, *av, **kw):
		return self.__fp__.seek(*av, **kw)

	def tell(self, *av, **kw):
		return self.__fp__.tell(*av, **kw)

	def close(self):
		if self.__fp__:
			self.__fp__.close()
			self.__fp__ = None

	def read(self, bufsiz = 65536):
		if self.tell() >= self.eoffset:
			return ''
		return self.__fp__.read(min(bufsiz, self.eoffset - self.tell()))

if __name__ == '__main__':
	import doctest
	doctest.testmod()
